Skip to content

Migrate Dashy to Vue 3, Vite and Node 24#2002

Merged
Lissy93 merged 90 commits intomasterfrom
migration/vue3-upgrade
Apr 26, 2026
Merged

Migrate Dashy to Vue 3, Vite and Node 24#2002
Lissy93 merged 90 commits intomasterfrom
migration/vue3-upgrade

Conversation

@Lissy93
Copy link
Copy Markdown
Owner

@Lissy93 Lissy93 commented Mar 9, 2026

Highlights

  • No more rebuild needed after making changes. And Docker image is 90% lighter.
  • Full Vue 2.7 → Vue 3.5 migration, plus Vuex 4, Vue Router 4 and vue-i18n 9 and all other deps
  • Replaced Vue CLI / Webpack with Vite 6, upgraded to Node 24, removes all old Node workarounds
  • Rebuilt config editing. New config editor, export menu, full schema-driven forms with validation
  • New responsive masonry layout for sections in home view
  • Settings menu consolidated 8 options into 1, for cleaner UI
  • Canonical /<view>/<page>/<section> URLs across views/configs/sections, with proper deep-linking and auth controls
  • Per-page config: theme, language, layout, icon size, favicon and address-bar colour
  • Server hardened, made more robust and reliable, no crashes and improves performance
  • Lazy-loaded everything that doesn't need to ship up front: translations, icons, schemas, editor forms
  • Stripped a pile of deps (axios, vue-js-modal, v-tooltip, vue-toasted, vue-material-tabs, rss-parser and friends), wrote lighter in-house replacements

Breaking Changes

Old env vars won't work client-side

Since the rebuild requirement was removed, this means that the frontend no longer has access to any env vars. Everything which could be, has been ported to the server. But the following env vars will now no longer be read. If you want to keep using them, you'll need to build Dashy from source yourself instead.

  • VITE_APP_ROUTING_MODE - Sets either hash vs history routing. Defaults to history.
  • VITE_APP_CONFIG_PATH - The location of the main config file. Defaults to conf.yml
  • VITE_APP_DOMAIN - The domain/URI where Dashy is hosted. Fallsback to window.location.origin
  • BASE_URL - The path to serve Dashy from. Defaults to /
  • VITE_APP_BASIC_AUTH_USERNAME / _PASSWORD - To auto-fill the basic auth form
  • VITE_APP_VERSION - Override current version num of Dashy running, injected at build-time (by us)

Docker Changes

  • The Docker container no longer rebuilds. If you were manually modifying any source files directly, then this will cease to work. You will need to either check the docs for the new way of doing this, or use your own built-from-source Dockerfile.
  • The Dockerfile now runs as a non-root user (uid/gid 1000). Bind-mounted host directories must be readable/writable by uid 1000, or override user: "1000:1000" with your own ids in your docker-compose. Existing volumes owned by root will fail.
  • The healthcheck endpoint has changed from node /app/services/healthcheck to node /app/services/healthcheck.js (note it now has the .js extension). So if your compose files or Kubernetes probes were hard-coding this, they'll need updating.

Config Changes

  • pageInfo.footerText renamed to pageInfo.footer
  • If footer not set, footer won't be shown (no more default footer). So we removed the old appConfig.hideComponents.hideFooter option.
  • section.displayData.rows is deprecated, because we now calculate rows dynamically based on available screen size
  • section.displayData.cols still works, but will be clamped to the page width (to prevent broken layouts on mobile).
  • Service worker now off by default, set appConfig.enableServiceWorker: true to enable and setup.
  • Schema now includes additionalProperties: false throughout, so you might see new warnings if you previously had some unknown/invalid attributes floating around

Issues Resolved


Main Changes Made

Vue 3 migration

  • Migrated the entire app from Vue 2.7 to Vue 3.5, all 161 components
  • Vuex 3 → Vuex 4, switched to createStore() API
  • Vue Router 3 → Vue Router 4, switched to createRouter() + history factories
  • vue-i18n 8 → vue-i18n 9, with the new legacy: false Composition API mode
  • Replaced Vue.use() registrations with app.use() plugin pattern
  • Swapped new Vue() for createApp() in main.js
  • Removed @vue/compat, app is now pure Vue 3 with no compat shims
  • Lifecycle hooks updated: beforeDestroybeforeUnmount, destroyedunmounted
  • slot="" syntax replaced with v-slot everywhere
  • <template v-for> now uses :key on the template (Vue 3 requirement)
  • Filters ({{ value | filter }}) are gone, replaced with plain methods/computeds
  • v-model updated to use the new modelValue/update:modelValue contract
  • Fixed <transition> class names: slide-enterslide-enter-from
  • Replaced deprecated ::v-deep with :deep() selector everywhere
  • Added explicit emits arrays to every component declaring custom events
  • Tightened up prop validation, every prop now has a type and sensible default
  • Global error handler now wired via app.config.errorHandler

Build system overhaul

  • Ripped out Vue CLI / Webpack, replaced with Vite 6
  • New vite.config.mjs with PWA, SVG loader, and user-data dev plugin
  • vitest.config.jsvitest.config.mjs, upgraded vitest 1.6 → 4
  • NODE_OPTIONS=--openssl-legacy-provider fully eliminated, no more crufty flags
  • process.env.VUE_APP_*import.meta.env.VITE_APP_* everywhere
  • defaults.js converted from CommonJS to ESM named exports
  • Custom Vite middleware to serve user-data/ YAML during dev
  • Font asset paths fixed: url('./assets/fonts/')url('@/assets/fonts/')
  • Node engine bumped from 16 → 18 minimum (24 works too)
  • index.html moved to repo root as the proper Vite entry point

Dependencies

  • Removed vue-js-modal, built our own lightweight Teleport-based modal
  • Removed v-tooltip, wrote our own zero-dependency v-tooltip directive
  • Removed vue-toasted, built our own toast plugin
  • Removed vue-material-tabs, wrote our own Tabs / TabItem components
  • Removed vue-json-tree-view, replaced with prettier YAML preview
  • Removed vue3-json-viewer, no longer needed
  • Removed @formschema/native, replaced with @jsonforms/vue
  • Removed v-jsoneditor and the broken JSON-tree experience that came with it
  • Removed rss-parser, wrote a tiny native DOMParser-based one (~80 lines)
  • Removed register-service-worker, doing it manually now
  • Removed connect-history-api-fallback, no longer needed with Vite
  • Removed @sentry/tracing, kept the Vue Sentry SDK (now lazy-loaded)
  • Removed remedial and entities (transitive cleanup)
  • Removed Clearbit logo CDN dep, was unreliable and tracking-heavy
  • Removed axios, replaced with custom client + server request wrappers
  • Added CodeMirror 6 + lang-yaml + lint + autocomplete for the new editor
  • Added @jsonforms/vue and @jsonforms/vue-vanilla for schema-driven forms
  • Added ajv-formats for stricter schema validation
  • Added yaml (eemeli/yaml) for proper YAML AST work
  • Added supertest for the new server endpoint tests
  • Bumped simple-icons 14 → 16
  • Bumped happy-dom 17 → 20 (security)
  • Bumped countless other deps to latest, see lockfile diff
  • Cleaned up resolutions block in package.json, only what's still needed

Performance

  • Lazy-load translation files, only the user's chosen language is fetched
  • Lazy-load OIDC and Keycloak code, never sent to non-auth users
  • Lazy-load Sentry / error reporting, only on first error
  • Lazy-load the entire schema JSON, only when the editor opens
  • Lazy-load simple-icons, the heaviest dep, only when an SI icon is rendered
  • Lazy-load all interactive editor components (modals, forms, etc)
  • Lazy-load the masonry mixin only on layouts that need it
  • Lazy-load EditModeSaveMenu and AddNewSection until edit mode is entered
  • Native loading="lazy" added to all item icon <img> tags

Settings menu

  • Consolidated the 8+ separate setting buttons into one neat options panel

Themes

  • Split the giant 2,200-line color-themes.scss into per-theme files

Layouts

  • New responsive masonry layout
  • Section column count is now picked up dynamically from CSS vars
  • Items reflow correctly when the viewport resizes mid-session

Sections

  • New section header with explicit collapse arrow, plus options ellipsis
  • Custom styles still supported, but sanitised before render

Multi-page / sub-page configs

  • Pages can now be addressed via canonical /<view>/<page>/<section> URLs
  • Sections inside sub-pages get full URL routing, including back-link
  • Switching between views preserves the active sub-page and section
  • Page-level theming, language, layout, icon size all stored per page

Page meta

  • Title now reflects active section / page / view consistently
  • New pageInfo.favicon config to set a custom favicon
  • New pageInfo.color config to set the browser address bar / theme colour
  • HTML lang attribute kept in sync with active language

Header and Footer

  • Default Dashy footer no longer rendered unless the user sets pageInfo.footer
  • Nav links use router-link for internal paths, and rel="noopener noreferrer" for external
  • Sub-pages auto-listed in the nav-links menu when present
  • Improved nav-link responsiveness on smaller screens

Config

  • New schema-driven UI editors for appConfig / pageInfo / pages
  • Powered by @jsonforms/vue with vanilla renderers and Dashy styling
  • pruneSchemaDefaults strips defaults + empties before save, keeps user YAML minimal
  • Restored proper local-override behaviour for the config store
  • Edit-mode now safely catches and surfaces config warnings/errors
  • Total redesign of export config menu, lists every config with easy export, view, edit and preview
  • Brand new config YAML editor, with validation and schema safeguards

Debug menu

  • Brand-new debug view, replaces the About page. Shows logs + data dump for debug

Auth

  • Cleaner OIDC initialisation flow, plus guest support and logout button fix
  • Bumped Keycloak 20 → 26, fixed KC logout button and role-based visibility

Server

  • Pulled the entire Express app out of server.js into services/app.js
  • Easier to test in isolation (which is exactly what the new tests do)
  • Global unhandled-rejection handler so the container can't silently die
  • Every endpoint catches and surfaces errors, no more circular crashes
  • Update checker switched to a proper semver compare
  • HTTP/HTTPS request module written in-house, gzip/deflate/brotli aware
  • Adds safeguards to prevent any possible kind of unexpected server crash
  • Closed a path traversal hole in save-config
  • Closed an SSRF hole in the CORS proxy

Linting

  • Set up modern flat-config ESLint, removed legacy stuff
  • Removed every // eslint-disable-line comment, fixed underlying issues
  • TypeScript checks via vue-tsc --noEmit, new lint and typecheck scripts

Schema-driven config

  • New ConfigSchema.json with proper JSON-Schema title / description / examples
  • Every appConfig / pageInfo / section / item / widget field is now described
  • Sub-items, custom search engines, hideFor* and showFor* all schemafied
  • additionalProperties: false everywhere, so typos in YAML get flagged
  • Schema is the single source of truth for both the YAML linter and the form editor
  • subItems, displayData, statusCheck* all properly modelled
  • Schema-driven hover tooltips in the YAML editor

Storage / store

  • configSource tracks the as-loaded YAML, separate from the runtime view
  • Item / widget IDs only added to the runtime view, never persisted to YAML
  • Local overrides layered cleanly: appConfig → quick-pickers → pageInfo → sections → pages
  • Per-page localStorage keys (appConfig-<pageId>, theme-<pageId>, etc)
  • clearScopedLocalConfig properly tidies up across all pages
  • stripRootOwnedFields ensures auth and pages always live on root only
  • safeClone util tolerates Vue's reactive proxies

Routing

  • Canonical /<view>/<page>/<section> URLs across home, minimal, workspace
  • Navigation guards now confirm before discarding edits when changing pages
  • Top progress bar (rsup-progress) hooked into all transitions
  • Lazy-loaded views fail gracefully with a helpful error toast
  • landing-page-* route swapped for a single landing redirector
  • startingView config now resolved at runtime from appConfig

Workspace view

  • Sidebar now respects hideFromWorkspace on both pages and sections
  • Sidebar reactive when sections change without a page reload
  • Default-section auto-open still works after the filter

Minimal view

  • Brought all home section features into minimal view: edit mode, save banner, sub-items
  • New responsive tab strip for sections, with scroll buttons on overflow
  • Tabs equal-width up to a point, then gracefully scroll
  • Section auto-selected from URL (/minimal/:page/:section)
  • Background image support honoured properly
  • "Show all" mode kicks in when searching
  • Edit-mode bottom banner now appears in minimal view too

PWA

  • Migrated to vite-plugin-pwa for service worker generation
  • Workbox config: NetworkFirst for YAML configs, with sensible 10MB cap
  • Service worker prompts user before activating new versions
  • Offline fallback to /index.html for SPA routes
  • Web manifest now includes proper Dashy icons + theme colour
  • Service worker registration is now manual

Docs

  • Re-wrote a bunch of docs to match the new changes

Dockerfile / containers

  • Switched to Node 24-alpine base, fully upgraded all deps
  • Removed the build requirement for run stage, now just starts
  • Removed all the legacy node workarounds
  • Adds OCI labels, and updates GitHub Actions docker workflow
  • Updates docker-compose.yml sample, as well as all docs

@liss-bot

This comment was marked as outdated.

@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 9, 2026

Deploy Preview for dashy-dev ready!

Name Link
🔨 Latest commit 20277ad
🔍 Latest deploy log https://app.netlify.com/projects/dashy-dev/deploys/69ee3752afbc8d00087297ff
😎 Deploy Preview https://deploy-preview-2002--dashy-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Lissy93

This comment was marked as resolved.

@liss-bot

This comment was marked as duplicate.

Repository owner deleted a comment from liss-bot Mar 11, 2026
Lissy93 added 4 commits March 29, 2026 18:34
transition class names (-enter → -enter-from), ::v-deep → :deep(), $router.currentRoute → $route, removed duplicate proxyReqEndpoint overrides that shadow the mixin

Plus some error handling and robustness improvments to global error handler, try-catch on all JSON.parse/yaml.load from localStorage/config, null-checks on DOM lookups, .catch() on unhandled axios promises, sanitized v-html in Footer

And removes stake weback and vue cli cleanup of crap.
This was referenced Apr 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment